package http;

import core.ByteBuf;
import core.Handler;
import core.HandlerContext;
import core.TemplateHandler;
import future.ListenableFutureImpl;
import javafx.util.Pair;
import service.BaseResponse;
import service.FastJsonUtils;
import service.ParameterHandler;
import service.ServiceContainer;
import util.FileUtil;
import util.Global;
import util.ObjectUtil;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;

public class HttpServerHandler extends TemplateHandler implements Handler {
    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
    private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9.]*");

    private final String url;

    public HttpServerHandler(String url){
        this.url = url;
    }
    @Override
    public void channelRead(HandlerContext ctx, Object msg) {
        super.channelRead(ctx,msg);
        // 读取时的业务处理
        if (msg instanceof HttpRequest request) {
            if (request.uri.startsWith("/service/")) {
                String requestType = request.method;
                String serviceUrl=request.uri.substring("/service".length());
                System.out.println("service url is : "+serviceUrl);
                // 定位接口位置
                Method targetMethod = ServiceContainer.getTargetMethod(serviceUrl, requestType);
                if (ObjectUtil.isEmpty(targetMethod)) {
                    sendError(ctx, 404);
                    return;
                }
                Object instance = ServiceContainer.getTargetInstanceByMethod(targetMethod);

                // 解析属性并注入至方法
                Object[] realParams = ParameterHandler.resolveRequestParam(request, targetMethod);
                Object result;
                try {
                    result = targetMethod.invoke(instance, realParams);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new RuntimeException(e);
                }
                if (!(result instanceof BaseResponse)) {
                    result = BaseResponse.success(result);
                }
                sendResponse(ctx, result);
                return;
            }

            //文件操作
            final String path;
            try {
                path = sanitizeUri(request.uri);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }

            System.out.println("local path："+path);
            if(path == null){
                sendError(ctx, 403);
                return;
            }

            if(Objects.equals(request.method, "POST")||Objects.equals(request.method, "PUT")){
                System.out.println("request.method:"+request.method);
                if (Global.limitFile.isEmpty() ||!Global.limitFile.contains(FileUtil.getFileExtension(path))) {
                    sendError(ctx,415);
                }
                else {
                    upload(ctx, request, path);
                }
                return;
            }

            /*
             静态资源对请求方法进行判断，如果不是get请求直接返回 405 错误
            */
            if(!Objects.equals(request.method, "GET")){
                sendError(ctx, 405);
                return;
            }

            File file = new File(path);
            if(file.isHidden()||!file.exists()){
                sendError(ctx, 404);
                return;
            }
            if(file.isDirectory()){
                if(request.uri.endsWith("/")){
                    sendList(ctx, file);
                }else{
                    this.write(ctx,HttpResponse.buildRedirectMessage(302,request.uri+'/'));
                    this.flush(ctx); //传递到头节点
                }
                return;
            }
            if(!file.isFile()){
                sendError(ctx, 403);
                return;
            }
            String range = request.getHeader("range");
            long fileSize = file.length();
            if (range!=null&&!range.isEmpty()||fileSize>HttpMessage.maxsize) {
                Pair<Long, Long> pair = HttpUtil.getRange(range);
                long from = pair.getKey();
                long to = pair.getValue();
                int targetLen = fileSize>HttpMessage.maxsize?HttpMessage.maxsize: (int) fileSize;
                if (to > from)
                    targetLen = (int) (to - from + 1);
                if (targetLen > HttpMessage.maxsize)
                    targetLen = HttpMessage.maxsize;
                if (to > 0 && from > to) {
                    //状态码200
                    HttpResponse response = new HttpResponse("200","OK");
                    response.setHeader("Content-Type",HttpUtil.DEFAULT_TYPE);
                    response.setHeader("Accept-Ranges", "bytes");
                    response.setHeader("Content-Length", String.valueOf(Long.toString(fileSize).length()));
                    response.setHeader("Content-Range", String.format("bytes 0-0/%d",fileSize));
                    response.setHeader("Connection", "keep-alive");
                    response.byteBuf=response.getBuf(String.valueOf(fileSize));
                    this.write(ctx,response.buildMessage());
                    this.flush(ctx); //传递到头节点
                }
                else
                {
                    to = from + targetLen - 1;
                    if (to >= fileSize - 1) {
                        to = fileSize - 1;
                        targetLen = (int) (to - from + 1);
                    }
                    HttpResponse response;
                    //断点续传206
                    response = new HttpResponse("206", "Partial Content");
                    response.setHeader("Content-Length", String.valueOf(targetLen));
                    response.setHeader("Accept-Ranges", "bytes");
                    response.setHeader("Content-Range", String.format("bytes %d-%d/%d",from,to,fileSize));
                    response.setHeader("Content-Type",HttpUtil.getMime(FileUtil.getFileExtension(file.getName()),HttpUtil.DEFAULT_TYPE));
                    response.setHeader("Connection", "keep-alive");

                    /*// 将HTTP响应写入Channel
                    this.write(ctx,response.buildMessage());
                    this.write(ctx,FileUtil.readFileByRandomAccess(file.getPath(), from, targetLen));
                    this.flush(ctx);//传递到头节点*/

                    // supplyAsync， 执行逻辑有返回值
                    // runAsync, 执行逻辑没有返回值
                    final int finalTargetLen=targetLen;
                    CompletableFuture.supplyAsync(() -> {
                        // 异步执行的任务
                        return FileUtil.readFileByRandomAccess(file.getPath(), from, finalTargetLen);
                            }).whenComplete((bytes,exception)->{
                        System.out.println("finalTargetLen: "+bytes.length);
                        this.write(ctx,response.buildMessage());
                        this.write(ctx,bytes);
                        this.flush(ctx);
                    });

                    /*// 实际上是阻塞串行执行
                    byte[] bytes=CompletableFuture.supplyAsync(() -> {
                        // 异步执行的任务
                        return FileUtil.readFileByRandomAccess(file.getPath(), from, finalTargetLen);
                    }).join();
                    this.write(ctx,bytes);
                    this.flush(ctx);*/
                }
            }
            else {
                HttpResponse response = new HttpResponse("200", "OK");
                response.setHeader("Content-Length", String.valueOf(fileSize));
                response.setHeader("Content-Type", HttpUtil.getMime(FileUtil.getFileExtension(file.getName()), HttpUtil.DEFAULT_TYPE));
                response.setHeader("Connection", "keep-alive");

                /*CompletableFuture.runAsync(() -> {
                    // 异步执行的任务
                    this.write(ctx,response.buildMessage());
                    this.write(ctx,FileUtil.readFileByRandomAccess(file.getPath(),0, (int) fileSize));
                    this.flush(ctx); //传递到头节点
                });*/

                /*CompletableFuture.supplyAsync(() -> {
                    // 异步执行的任务
                    return FileUtil.readFileByRandomAccess(file.getPath(),0, (int) fileSize);
                }).whenComplete((bytes,exception)->{
                    this.write(ctx,response.buildMessage());
                    this.write(ctx,bytes);
                    this.flush(ctx);
                });*/

                ListenableFutureImpl<byte[]> listenableFuture = new ListenableFutureImpl<>(() -> {
                    // 异步执行的操作
                    return FileUtil.readFileByRandomAccess(file.getPath(),0, (int) fileSize);
                });

                // 添加监听器，任务完成时调用
                listenableFuture.start().addListener((result)-> {
                    // 当操作完成时执行的监听器
                    System.out.println("Async operation completed!");
                    this.write(ctx,response.buildMessage());
                    this.write(ctx,result);
                    this.flush(ctx);
                });

            }
        }
        else {
            // 响应回复语
            String str = "hello World!";
            String response = String.format("HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html;charset=UTF-8\r\n\r\n%s", str.length(), str);
            this.write(ctx,response); //传递到上一个
            this.flush(ctx); //传递到头节点
            //ctx.channel.doFlush(); //在头节点调用
        }
    }

    @Override
    public void write(HandlerContext ctx, Object msg) {
        // 写出时的业务处理
        super.write(ctx,msg);
        // 传递给上一个
        ctx.write(msg);
    }

    @Override
    public void flush(HandlerContext ctx) {
        // 执行写出时处理
        super.flush(ctx);
        // 传递给上一个
        ctx.flush();
    }

    private String sanitizeUri(String uri) throws UnsupportedEncodingException {
        uri = URLDecoder.decode(uri, StandardCharsets.UTF_8); //对URL进行编码
        if(uri.contains("..")){
            return null;
        }
        uri= url +uri;

        uri = uri.replace('/',File.separatorChar);
        if(uri.contains(File.separator+'.')||uri.contains('.'+File.separator)||uri.startsWith(".")||uri.endsWith(".")
                ||INSECURE_URI.matcher(uri).matches()){
            return null;
        }
        return File.separator+uri;
    }

    private void sendList(HandlerContext ctx, File dir){
        HttpResponse response = new HttpResponse("200","OK");
        response.header.put("Content-Type", new ArrayList<>(List.of("text/html;charset=utf-8")));
        StringBuilder buf = new StringBuilder();
        String dirPath = dir.getPath();
        buf.append("<!DOCTYPE html>\r\n");
        buf.append("<html><head><title>");

        buf.append(dirPath);
        buf.append("</title></head><body style='background-color:#eeeeee;'>\r\n");
        buf.append("<h3 style='background-color:#333333; color:white; padding:10px; border-radius:5px;'>");
        //buf.append("当前路径：");
        buf.append("\u5F53\u524D\u8DEF\u5F84\uFF1A");
        buf.append(dirPath);
        buf.append("</h3>\r\n");
        buf.append("<ul>");
        buf.append("<li><a href=\"../\">⬅︎ 上一级</a></li>\r\n");
        DecimalFormat df = new DecimalFormat("######0.00");
        for(File f : Objects.requireNonNull(dir.listFiles())){
            if(f.isHidden()||!f.exists()){
                continue;
            }
            String name = f.getName();
            //校验文件名
            //if(!ALLOWED_FILE_NAME.matcher(name).matches()) continue;
            buf.append("<li><div style=\"display:flex\"><div style='min-width:300px;'>");
            if(f.isDirectory()){
                buf.append("\uD83D\uDCC1");
            }else{
                buf.append("\uD83D\uDCC4");
            }
            buf.append("<a style='margin-left:5px;' href=\"");
            buf.append(name);
            buf.append("\">");
            buf.append(name);
            buf.append("</a></div><div style='margin-left:10px; font-weight:bold;'>");
            if(!f.isDirectory()){
                double v = f.length() / 1024.0;
                buf.append(df.format(v));
                buf.append("KB");
            }
            buf.append("</div></div></li>\r\n");
        }
        buf.append("</ul></body></html>\r\n");
        response.byteBuf= HttpMessage.getBuf(buf.toString(),StandardCharsets.UTF_8);
        //System.out.println(StringUtil.getEncoding(response.content));
        response.header.put("Content-Length", new ArrayList<>(List.of(String.format("%d",response.byteBuf.getCanReadSize()))));

        this.write(ctx,response.buildMessage());
        this.flush(ctx);  //传递到头节点
    }

    private void sendResponse(HandlerContext ctx, Object response) {

        String json = FastJsonUtils.getBeanToJson(response);
        HttpResponse httpResponse = new HttpResponse("200","OK");
        httpResponse.byteBuf= HttpMessage.getBuf(json,StandardCharsets.UTF_8);
        httpResponse.header.put("Content-Type", new ArrayList<>(List.of("application/json")));
        httpResponse.header.put("Content-Length", new ArrayList<>(List.of(String.format("%d",httpResponse.byteBuf.getCanReadSize()))));
        this.write(ctx,httpResponse.buildMessage());
        this.flush(ctx);
    }

    private void sendResponse(HandlerContext ctx, String str,String type) {
        // body采用utf-8编码
        String response = String.format("HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: %s;charset=UTF-8\r\n\r\n%s", str.getBytes(StandardCharsets.UTF_8).length,type, str);
        this.write(ctx,response); //传递到上一个
        this.flush(ctx); //传递到头节点
    }

    private void sendError(HandlerContext ctx,int errCode){

        this.write(ctx,HttpResponse.buildErrorMessage(errCode));
        this.flush(ctx);
    }

    private void upload(HandlerContext ctx,HttpRequest request,String fileName) {

        int targetLen = 0;
        String range=request.getHeader("range");
        Pair<Long, Long> pair = HttpUtil.getRange(range);
        long from = pair.getKey();
        long to = pair.getValue();
        if (to > 0)
        {
            targetLen = (int) (to - from + 1);
        }

        ByteBuf buf=new ByteBuf();
        if (request.parseForm(new String(request.byteBuf.toByteArray(), HttpMessage.charset), buf).isEmpty()) {
            buf.put(request.byteBuf.toByteArray());
        }

        if (targetLen==0) FileUtil.removeFile(fileName);

        int finalTargetLen = (targetLen==0)?buf.getCanReadSize():targetLen;
        System.out.println("fileName: "+fileName+"\ttargetLen:"+finalTargetLen);
        /*int result=FileUtil.writeFileByRandomAccess(fileName, from, buf.get(),finalTargetLen);
        if (result>0) {
            String str="{\"code\":\"0\",\"msg\":\"ok\"}";
            sendResponse(ctx,str);
        } else {
            sendError(ctx,500);
        }*/

        CompletableFuture.supplyAsync(() -> {
            // 异步执行的任务
            return FileUtil.writeFileByRandomAccess(fileName, from, buf.get(),finalTargetLen);
        }).whenComplete((result,exception)->{
            if (result>0) {
                String str="{\"code\":\"0\",\"msg\":\"ok\"}";
                sendResponse(ctx,str,HttpUtil.getMime("json"));
            } else {
                sendError(ctx,500);
            }
        });

    }

}
